// ignore_for_file: non_constant_identifier_names

import 'package:dart_bean/feature/custom_json_converter.dart';
import 'package:dart_bean/feature/enum_field.dart';
import 'package:dart_bean/feature/json_field.dart';
import 'package:dart_bean/feature/json_key.dart';
import 'package:dart_bean/util/ext.dart';
import 'package:dart_bean/field_info.dart';
import 'package:dart_bean/setting.dart';
import 'package:dart_bean/flow/gen_code_flow.dart';

abstract class FieldFeature {
  ///仲裁者<br>
  ///仲裁者模式 https://blog.csdn.net/guorui_java/article/details/104247966
  late final FieldFeature mediator;

  String? getJsonName(FieldInfo field) => null;

  ///DemoBean -> DemoBean.fromJson <br>
  ///DemoEnum -> _$DemoEnumFromJson
  String? getFromJsonMethod(FieldInfo field, String type) => null;

  /// the value from json, eg: it.a = json['a'] as int; jsonParam is "json['a']"
  String? getJsonParam(FieldInfo field) => null;

  String? getConvert(FieldInfo field) => null;

  String? toJsonRightValue(FieldInfo field, String instance, String type) => null;

  String? mergeToJsonRightValue(FieldInfo field, String instance, String type) => null;

  bool? isIgnoreSerialize(FieldInfo field) => null;

  bool? isIgnoreDeserialize(FieldInfo field) => null;

  String getNotNullType(String type) {
    if (type.endsWith('?')) return type.substring(0, type.length - 1);
    return type;
  }

  void buildDefaultValue(FieldInfo field, List<String> params) {
    if (autoDefaultValueStyle && field.hasDefaultValue) params.add('buildDefaultValue:false');
  }
}

class _DefaultFieldFeature extends FieldFeature {
  @override
  String getJsonName(FieldInfo field) {
    return field.name;
  }

  @override
  bool isIgnoreDeserialize(FieldInfo field) {
    return field.self.isStatic;
  }

  @override
  bool isIgnoreSerialize(FieldInfo field) {
    return field.self.isStatic;
  }

  static final _listTypeReg = RegExp(r'List<(@<subType>\w+)(@<subNullable>\?)?>').fixNamedGroup();

  @override
  String? getFromJsonMethod(FieldInfo field, String type) {
    final fieldType = getNotNullType(type);
    if (fieldType.isEmpty || _isBaseType(fieldType) || _isMapType(fieldType) || _isSetType(fieldType)) return null;
    return '$fieldType.fromJson';
  }

  @override
  String? getJsonParam(FieldInfo field) {
    return "$fromJsonParamName['${mediator.getJsonName(field)}']";
  }

  @override
  String getConvert(FieldInfo field) {
    final String json = mediator.getJsonParam(field)!;
    final listTypeMatch = _listTypeReg.firstMatch(field.type);
    final jsonConvert = jsonCastParamName;
    List<String> params = [json];
    String convert, type;
    if (listTypeMatch != null) {
      convert = 'convertList';
      final subType = listTypeMatch.namedGroup('subType')!;
      final fromJson = mediator.getFromJsonMethod(field, subType);
      if (fromJson != null) params.add('fromJson:$fromJson');
      bool subNullable = listTypeMatch.namedGroup('subNullable') != null;
      type = subNullable ? '$subType?' : subType;
    } else {
      convert = 'convert';
      final fieldType = getNotNullType(field.type);
      type = fieldType;
      final fromJson = mediator.getFromJsonMethod(field, type);
      if (fromJson != null) params.add('fromJson:$fromJson');
    }
    buildDefaultValue(field, params);
    return "$jsonConvert.$convert<$type>(${params.join(',')})";
  }

  @override
  String toJsonRightValue(FieldInfo field, String instance, String type) {
    final fieldType = getNotNullType(type);
    final NULL = type.endsWith('?') ? '?' : '';
    if (fieldType == 'DateTime') {
      return "$jsonCastParamName.toJson($instance)";
    } else if (fieldType.isEmpty || _isBaseType(fieldType) || _isMapType(fieldType) || _isSetType(fieldType)) {
      return instance;
    } else {
      return "$instance$NULL.toJson()";
    }
  }

  @override
  String mergeToJsonRightValue(FieldInfo field, String instance, String type) {
    final listTypeMatch = _listTypeReg.firstMatch(type);
    final NULL = type.endsWith('?') ? '?' : '';
    if (listTypeMatch != null) {
      final subType = listTypeMatch.namedGroup('subType')!;
      final childParamName = 'v';
      final childRightValue = mediator.toJsonRightValue(field, childParamName, subType);
      if (childRightValue == childParamName) return instance;
      return "$instance$NULL.map(($childParamName) => $childRightValue).toList()";
    } else {
      return mediator.toJsonRightValue(field, instance, type)!;
    }
  }

  bool _isBaseType(String type) {
    return _BASE_TYPES.contains(type);
  }

  bool _isMapType(String type) {
    return type.startsWith('Map<') || type == 'Map';
  }

  bool _isSetType(String type) {
    return type.startsWith('Set<') || type == 'Set'; //fixme list like type, should check sub type
  }

  final _BASE_TYPES = ["int", "num", "double", "String", "bool", "List", "DateTime", "Null", "var", "dynamic"];
}

class MediatorFieldFeature extends FieldFeature {
  static final it = MediatorFieldFeature._();
  final List<FieldFeature> _fieldFeatures = [];

  MediatorFieldFeature._() {
    _initFeatures();
  }

  void _initFeatures() {
    _fieldFeatures.add(CodeFlowFeature()); //add to first ensure running
    _fieldFeatures.add(JsonFieldFeature());
    _fieldFeatures.add(JsonKeyFeature());
    _fieldFeatures.add(EnumFieldFeature());
    _fieldFeatures.add(CustomJsonConverterFeature());
    _fieldFeatures.add(_DefaultFieldFeature());

    for (var f in _fieldFeatures) {
      f.mediator = this;
    }
  }

  @override
  String getJsonName(FieldInfo field) {
    for (var f in _fieldFeatures) {
      final v = f.getJsonName(field);
      if (v != null) return v;
    }
    throw UnimplementedError('unimplemented');
  }

  @override
  String? getFromJsonMethod(FieldInfo field, String type) {
    for (var f in _fieldFeatures) {
      final v = f.getFromJsonMethod(field, type);
      if (v != null) return v;
    }
    return null;
  }

  @override
  String toJsonRightValue(FieldInfo field, String instance, String type) {
    for (var f in _fieldFeatures) {
      final v = f.toJsonRightValue(field, instance, type);
      if (v != null) return v;
    }
    throw UnimplementedError('unimplemented');
  }

  @override
  String mergeToJsonRightValue(FieldInfo field, String instance, String type) {
    for (var f in _fieldFeatures) {
      final v = f.mergeToJsonRightValue(field, instance, type);
      if (v != null) return v;
    }
    throw UnimplementedError('unimplemented');
  }

  @override
  String? getJsonParam(FieldInfo field) {
    for (var f in _fieldFeatures) {
      final v = f.getJsonParam(field);
      if (v != null) return v;
    }
    throw UnimplementedError('unimplemented');
  }

  @override
  String getConvert(FieldInfo field) {
    for (var f in _fieldFeatures) {
      final v = f.getConvert(field);
      if (v != null) return v;
    }
    throw UnimplementedError('unimplemented');
  }

  @override
  bool isIgnoreDeserialize(FieldInfo field) {
    for (var f in _fieldFeatures) {
      final v = f.isIgnoreDeserialize(field);
      if (v == true) return v!;
    }
    return false;
  }

  @override
  bool isIgnoreSerialize(FieldInfo field) {
    for (var f in _fieldFeatures) {
      final v = f.isIgnoreSerialize(field);
      if (v == true) return v!;
    }
    return false;
  }
}
